package luj.tool.jnproxyan.proxy.network.socksv2.server.handler;

import com.google.common.collect.ImmutableSet;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialResponse;
import io.netty.handler.codec.socksx.v5.Socks5AuthMethod;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5InitialRequest;
import io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder;
import luj.cluster.api.actor.Tellable;
import luj.tool.jnproxyan.framework.lujnet.ConnState;
import luj.tool.jnproxyan.proxy.network.socks.actor.root.message.request.init.add.AddRequestMsg;
import luj.tool.jnproxyan.proxy.network.socksv2.connection.ConnStateAttr;
import luj.tool.jnproxyan.framework.proxy.actor.tell.ActorTeller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * @see luj.tool.jnproxyan.framework.lujnet.receive.Recv33Methods
 */
@Component
public class S5InitialReqHandler extends SimpleChannelInboundHandler<Socks5InitialRequest> {

  public S5InitialReqHandler inject(Tellable socksRef) {
    var result = new S5InitialReqHandler();
    result._socksRef = socksRef;

    result._actorTeller = _actorTeller;
    result._cmdReqHandler = _cmdReqHandler;

    return result;
  }

  // https://www.ietf.org/rfc/rfc1928.txt
  // 3.  Procedure for TCP-based clients
  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Socks5InitialRequest msg) {
    Channel c2pConn = ctx.channel();
    Set<Socks5AuthMethod> methods = ImmutableSet.copyOf(msg.authMethods());

    ConnState connState = new ConnStateAttr(c2pConn).get();
    connState.setMethods(methods);

    // 记录请求
    requestAddNew(c2pConn, connState);

    if (!methods.contains(Socks5AuthMethod.NO_AUTH)) {
      var rsp = new DefaultSocks5InitialResponse(Socks5AuthMethod.UNACCEPTED);
      c2pConn.writeAndFlush(rsp);
      c2pConn.close();
      return;
    }

    var rsp = new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH);
    c2pConn.writeAndFlush(rsp);

    ChannelPipeline pipeline = ctx.pipeline();
    pipeline.remove(this);
    pipeline.replace(Socks5InitialRequestDecoder.class, null, new Socks5CommandRequestDecoder());
    pipeline.addLast(_cmdReqHandler.inject(_socksRef));
  }

  /**
   * @see luj.tool.jnproxyan.proxy.network.socks.actor.root.message.request.init.add.OnAddRequest#onHandle
   */
  private void requestAddNew(Channel conn, ConnState state) {
    long now = System.currentTimeMillis();

    _actorTeller.inject(_socksRef).tell(AddRequestMsg.class, (b, m) -> b
        .f(m::reqId).set(state.getReqId())
        .f(m::time).set(now)
        .f(m::c2pConnV2).set(conn)
    );
  }

  Tellable _socksRef;

  @Autowired
  ActorTeller _actorTeller;

  @Autowired
  S5CommandReqHandler _cmdReqHandler;
}
